﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Net;
using System.Net.Cache;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;

namespace Cryomancer.StarChart
{
	/// <summary>
	/// Singleton class that manages access to the AAVSO VSP
	/// </summary>
	public sealed class VSPAccessMaster : IVSPAccessMaster
	{
		#region Constants
		private const string VSPAddress = "http://aavso.org/vsp";
		//private const string VSPAddress = "http://localhost:9898";
		#endregion

		#region Events
		public static event EventHandler<VSPChartReadyEventArgs> ChartReady
		{
			add
			{
				IVSPAccessMaster instance = GetInstance();
				instance.ChartReady += value;
			}
			remove
			{
				IVSPAccessMaster instance = GetInstance();
				instance.ChartReady -= value;
			}
		}
		#endregion

		#region Members

		#region Singleton Members
		private static object          syncObj;
		private static VSPAccessMaster theInstance;
		#endregion

		private Regex            imageParseRegex;
		private BackgroundWorker worker;

		private event EventHandler<VSPChartReadyEventArgs> chartReady;
		#endregion

		#region Properties
		/// <summary>
		/// Gets whether a background request for a chart is in progress.
		/// </summary>
		public static bool FetchInProgress
		{
			get
			{
				IVSPAccessMaster instance = GetInstance();

				return instance.FetchInProgress;
			}
		}

		#endregion

		#region C'Tors and object lifetime code

		static VSPAccessMaster()
		{
			syncObj = new object();
		}

		private VSPAccessMaster()
		{
			this.imageParseRegex = new Regex(@"\<a href=""(?<link>.*)""\><img src=""(?<chart>.*)""/\>\</a\>", RegexOptions.Compiled);

			this.worker = new BackgroundWorker();
			worker.WorkerReportsProgress      = false;
			worker.WorkerSupportsCancellation = false;
			worker.DoWork             += new DoWorkEventHandler(Worker_DoWork);
			worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(Worker_RunWorkerCompleted);
		}

		#endregion

		#region Methods
		/// <summary>
		/// Singleton factory method to get a reference to the VSPAccessMaster
		/// </summary>
		/// <returns>A reference to the VSPAccessMaster</returns>
		public static VSPAccessMaster GetInstance()
		{
			if (theInstance == null)
			{
				lock (syncObj)
				{
					if (theInstance == null)
					{
						theInstance = new VSPAccessMaster();
					}
				}
			}

			return theInstance;
		}

		/// <summary>
		/// Gets a chart from the VSP system
		/// </summary>
		/// <param name="request">An initialized Request</param>
		/// <returns>An AAVSO chart</returns>
		public static VSPChartDetail GetChart(VSPChartDetail request)
		{
			IVSPAccessMaster instance = GetInstance();

			return instance.GetChart(request);
		}

		/// <summary>
		/// Gets a chart from the VSP system asynchronously.
		/// </summary>
		/// <param name="request">An initialized request</param>
		/// <returns>True if a request was dispatched, false otherwise</returns>
		public static bool GetChartAsync(VSPChartDetail request)
		{
			IVSPAccessMaster instance = GetInstance();

			return instance.GetChartAsync(request);
		}

		private void OnChartReady(VSPChartReadyEventArgs args)
		{
			if (this.chartReady != null)
			{
				chartReady(this, args);
			}
		}

		private VSPChartReadyEventArgs ThreadProc(object detail)
		{
			VSPChartDetail request = detail as VSPChartDetail;
			if (request == null)
			{
				return new VSPChartReadyEventArgs(request, new VSPAccessException("Chart Request was not a VSPChartDetail object."));
			}

			try
			{
				VSPChartDetail result = GetChart(request);

				return new VSPChartReadyEventArgs(result);
			}
			catch (Exception err)
			{
				return new VSPChartReadyEventArgs(request, new VSPAccessException("Failed to get VSP Chart.", err));
			}
		}

		private Image GetChartFromFileSystem(VSPChartDetail request)
		{
			string filePath = SavedChartPath(request);
			if (!File.Exists(filePath))
			{
				return null;
			}

			try
			{
				return new Bitmap(filePath);
			}
			catch
			{
				//TODO: At least log this or something first
				return null;
			}
		}

		private string SavedChartPath(VSPChartDetail detail)
		{
			if (detail == null)
			{
				return String.Empty;
			}

			//TODO: Create a configuration element for this value
			string folderPath = Path.Combine(
				Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
				"StarChart");

			if (!Directory.Exists(folderPath))
			{
				Directory.CreateDirectory(folderPath);
			}

			string fullPath = Path.Combine(folderPath, detail.ToKeyString());

			return fullPath;
		}

		private void SaveVSPChart(VSPChartDetail detail, Image chart)
		{
			string savedChartPath = SavedChartPath(detail);

			chart.Save(savedChartPath);
		}

		private Image GetChartFromVSP(VSPChartDetail request)
		{
			try
			{
				//The request for the chart seems to go like this:
				//Req: Get /vsp
				//Rsp: Ok, here's your session cookies.
				//Req: POST the parameter list to /vsp
				//Rsp: 302 Moved to /vsp/chart
				//Req: Get /vsp/chart (the request data is now in a cookie)
				//Rsp: Ok

				HttpWebRequest req = (HttpWebRequest)WebRequest.Create(VSPAddress);
				req.Method = "GET";

				string cookieHeader = String.Empty;
				using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
				{
					cookieHeader = resp.Headers["Set-Cookie"];
					//using (StreamReader reader = new StreamReader(resp.GetResponseStream()))
					//{
					//    reader.ReadToEnd();
					//}
				}

				string reqString = request.ToUrlString();
				byte[] reqData = Encoding.UTF8.GetBytes(reqString);


				//First Request
				HttpWebRequest req1 = (HttpWebRequest)WebRequest.Create(VSPAddress);
				req1.Method            = "POST";
				req1.ContentLength     = reqData.Length;
				req1.ContentType       = "application/x-www-form-urlencoded";
				req1.Accept            = "application/x-ms-application, image/jpeg, application/xaml+xml, image/gif, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/msword, */*";
				req1.UserAgent         = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; InfoPath.3)";
				req1.AllowAutoRedirect = false;
				req1.Headers.Add("Cookie", cookieHeader);

				using (Stream reqStream = req1.GetRequestStream())
				{
					reqStream.Write(reqData, 0, reqData.Length);
					reqStream.Flush();
				}

				//First Response
				string           location = String.Empty;
				string           respStr  = String.Empty;
				HttpStatusCode   status   = HttpStatusCode.Unused;
				using (HttpWebResponse resp1 = (HttpWebResponse)req1.GetResponse())
				{
					location = resp1.Headers["Location"];
					status   = resp1.StatusCode;
					//using (StreamReader reader = new StreamReader(resp1.GetResponseStream()))
					//{
					//    respStr = reader.ReadToEnd();
					//}
				}

				//Second Request
				HttpWebRequest req2 = (HttpWebRequest)WebRequest.Create(location);
				req2.Method            = "GET";
				req2.Referer           = VSPAddress;
				req2.Headers["Cookie"] = cookieHeader;
				
				//Second Response
				using (HttpWebResponse resp2 = (HttpWebResponse)req2.GetResponse())
				{
					using (StreamReader reader = new StreamReader(resp2.GetResponseStream()))
					{
						respStr = reader.ReadToEnd();
					}
				}

				string imageUrl = String.Empty;
				Match  match    = this.imageParseRegex.Match(respStr);
				if (match.Success)
				{
					imageUrl = match.Groups["chart"].Value;
				}
				else
				{
					//TODO: Deal with this error!
				}

				HttpWebRequest imageReq = (HttpWebRequest)WebRequest.Create(imageUrl);
				imageReq.Method = "GET";

				Image retVal = null;
				using (HttpWebResponse resp = (HttpWebResponse)imageReq.GetResponse())
				{
					retVal = new Bitmap(resp.GetResponseStream());
				}

				return retVal;
			}
			catch
			{
				throw new Exception();
			}
		}
		#endregion

		#region Event Handlers
		private void Worker_DoWork(object sender, DoWorkEventArgs e)
		{
			VSPChartDetail detail = e.Argument as VSPChartDetail;
            
            e.Result = ThreadProc(detail);
		}

		private void Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			if (e.Error != null)
			{
				//TODO: Deal with error
			}

			VSPChartReadyEventArgs result = e.Result as VSPChartReadyEventArgs;
			if (result == null)
			{
				//TODO: Deal with error
			}

			OnChartReady(result);
		}
		#endregion

		#region IVSPAccessMaster methods
		event EventHandler<VSPChartReadyEventArgs> IVSPAccessMaster.ChartReady
		{
			add
			{
				chartReady += value;
			}
			remove
			{
				chartReady -= value;
			}
		}

		/// <summary>
		/// Gets whether a background request for a chart is in progress.
		/// </summary>
		bool IVSPAccessMaster.FetchInProgress
		{
			get
			{
				return this.worker.IsBusy;
			}
		}

		/// <summary>
		/// Gets a chart from the VSP system
		/// </summary>
		/// <param name="request">An initialized Request</param>
		/// <returns>An AAVSO chart</returns>
		VSPChartDetail IVSPAccessMaster.GetChart(VSPChartDetail request)
		{
			if (request == null)
			{
				throw new ArgumentNullException("request");
			}

			Image chart = GetChartFromFileSystem(request);
			if (chart == null)
			{
				chart = GetChartFromVSP(request);
				SaveVSPChart(request, chart);
			}

			VSPChartDetail newDetail = request.GetDuplicate();
			newDetail.ChartImage = chart;

			return newDetail;
		}

		/// <summary>
		/// Gets a chart from the VSP system asynchronously.
		/// </summary>
		/// <param name="request">An initialized request</param>
		/// <returns>True if a request was dispatched, false otherwise</returns>
		bool IVSPAccessMaster.GetChartAsync(VSPChartDetail request)
		{
			if (this.worker.IsBusy)
			{
				return false;
			}

			this.worker.RunWorkerAsync(request);
			return true;
		}
		#endregion
	}

	public interface IVSPAccessMaster
	{
		bool           FetchInProgress
		{
			get;
		}
		event          EventHandler<VSPChartReadyEventArgs> ChartReady;
		VSPChartDetail GetChart(VSPChartDetail request);
		bool           GetChartAsync(VSPChartDetail request);
	}

	public class VSPChartReadyEventArgs : EventArgs
	{
		#region Members
		private VSPChartDetail detail;
		private Image          chart;
		private Exception      error;
		#endregion

		#region Properties
		/// <summary>
		/// Gets the detail that generated this request.
		/// </summary>
		public VSPChartDetail Detail
		{
			get
			{
				return this.detail;
			}
		}

		/// <summary>
		/// Gets any exception that may have occured.
		/// </summary>
		public Exception Error
		{
			get
			{
				return this.error;
			}
		}

		#endregion

		#region C'Tors and object lifetime code
		public VSPChartReadyEventArgs(VSPChartDetail detail, Exception err) :
			this(detail)
		{
			this.error = err;
		}

		public VSPChartReadyEventArgs(VSPChartDetail detail) :
			this()
		{
			this.detail = detail;
		}

		protected VSPChartReadyEventArgs()
		{

		}
		#endregion
	}
}
